package rad.framework.uml2.classdiagram.util;

import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import rad.framework.uml2.uml.edit.UMLEditPlugin;

import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Image;
import org.eclipse.uml2.uml.AggregationKind;
import org.eclipse.uml2.uml.Association;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.ParameterDirectionKind;
import org.eclipse.uml2.uml.PrimitiveType;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.Type;

public class ModelUtil {

	public static final String ENTITY = "javax.persistence.Entity";

	public static final String INHERITANCE = "javax.persistence.Inheritance";

	public static final String ONE_TO_MANY = "javax.persistence.OneToMany";

	public static final String MANY_TO_ONE = "javax.persistence.ManyToOne";

	public static final String MANY_TO_MANY = "javax.persistence.ManyToMany";

	public static final String ONE_TO_ONE = "javax.persistence.OneToOne";

	public static final String STATELESS = "javax.ejb.Stateless";

	public static final String WEBSERVICE = "javax.jws.WebService";

	public static final String WEBMETHOD = "javax.jws.WebMethod";

	public static void applyStereotype(Element element, String name) {
		for (Iterator applicableStereotypes = element
				.getApplicableStereotypes().iterator(); applicableStereotypes
				.hasNext();) {

			Stereotype stereotype = (Stereotype) applicableStereotypes.next();

			if (stereotype.getName().equals(name)) {
				element.applyStereotype(stereotype);
			}
		}

	}

	public static void unapplyStereotypes(Element element) {
		List<Stereotype> applied = element.getAppliedStereotypes();
		for (Stereotype s : applied) {
			// TODO has undo problems
			element.unapplyStereotype(s);
		}
	}

	public static boolean isStereotypeApplied(Element element, String name) {
		Stereotype stereotype = getAppliedStereotype(element, name);
		if (stereotype != null) {
			return true;
		}
		return false;
	}

	public static Stereotype getAppliedStereotype(Element element, String name) {
		for (Iterator<Stereotype> applicableStereotypes = element
				.getAppliedStereotypes().iterator(); applicableStereotypes
				.hasNext();) {
			Stereotype stereotype = (Stereotype) applicableStereotypes.next();
			if (stereotype.getName().equals(name)) {
				return stereotype;
			}
		}
		return null;
	}

	public static Association createOneToMany(Type source, Type target) {
		Association association = source.createAssociation(true,
				AggregationKind.COMPOSITE_LITERAL, "children", 0, -1, target,
				true, AggregationKind.NONE_LITERAL, "parent", 1, 1);
		applyStereotype(association.getMember("children"), ONE_TO_MANY);
		applyStereotype(association.getMember("parent"), MANY_TO_ONE);
		return association;
	}

	public static Association createManyToOne(Type source, Type target) {
		Association association = source.createAssociation(true,
				AggregationKind.NONE_LITERAL, "target", 0, 1, target, false,
				AggregationKind.NONE_LITERAL, "source", 0, -1);
		applyStereotype(association.getMember("target"), MANY_TO_ONE);
		return association;
	}

	public static Association createManyToMany(Type source, Type target) {
		Association association = source.createAssociation(true,
//				AggregationKind.NONE_LITERAL, "target", 0, 1, target, true,
				AggregationKind.NONE_LITERAL, "target", 0, -1, target, true,
				AggregationKind.NONE_LITERAL, "source", 0, -1);
		applyStereotype(association.getMember("target"), MANY_TO_MANY);
		applyStereotype(association.getMember("source"), MANY_TO_MANY);
		return association;
	}

	public static Association createOneToOne(Type source, Type target) {
		Association association = source.createAssociation(true,
				AggregationKind.NONE_LITERAL, "left", 0, 1, target, true,
				AggregationKind.NONE_LITERAL, "right", 0, 1);
		applyStereotype(association.getMember("left"), ONE_TO_ONE);
		applyStereotype(association.getMember("right"), ONE_TO_ONE);
		return association;
	}

	public static Association createUseCaseActorAssociation(Type source,
			Type target) {
		Association association = source.createAssociation(true,
				AggregationKind.NONE_LITERAL, "", 1, 1, target, false,
				AggregationKind.NONE_LITERAL, "", 1, 1);
		return association;
	}

	/**
	 * A OneToMany association will have one member end with stereotype
	 * OneToMany and one member end with stereotype ManyToOne.
	 * 
	 * @param association
	 * @return
	 */
	public static boolean isOneToMany(Association association) {
		return checkCardinality(association, true, false);
	}

	public static boolean isCompositeOrShared(Property property) {
		return property.isComposite()
				|| AggregationKind.SHARED_LITERAL.equals(property
						.getAggregation());
	}

	/**
	 * A ManyToOne association will only have one member end with stereotype
	 * ManyToOne and no OneToMany, because it is not navigable.
	 * 
	 * @param association
	 * @return
	 */
	public static boolean isManyToOne(Association association) {
		return checkCardinality(association, false, true);
	}

	/**
	 * A ManyToMany association will two member ends with stereotype ManyToMany.
	 * 
	 * @param association
	 * @return
	 */
	public static boolean isManyToMany(Association association) {
		return checkCardinality(association, true, true);
	}

	/**
	 * A OneToOne association will two member ends with stereotype OneToOne.
	 * 
	 * @param association
	 * @return
	 */
	public static boolean isOneToOne(Association association) {
		return checkCardinality(association, false, false);
	}

	protected static boolean checkCardinality(Association association,
			boolean firstMulti, boolean secondMulti) {
		Property first = association.getMemberEnds().get(0);
		Property second = association.getMemberEnds().get(1);
		boolean fb = first.isMultivalued();
		boolean sb = second.isMultivalued();
		if (fb != firstMulti) {
			return false;
		}
		if (sb != secondMulti) {
			return false;
		}
		return true;
	}

	public static String getPropertyLabel(Property property) {
		String text = "";
		Type type = property.getType();
		if (null != type) {
			String typeName = type.getName();
			if (typeName.length() > 0) {
				text = text + " : " + typeName;
			}
		}
		return text;
	}

	public static String getSignature(Operation operation) {
		String params = "(";
		String returnType = "void";
		for (Iterator iter = operation.getOwnedParameters().iterator(); iter
				.hasNext();) {
			Parameter parameter = (Parameter) iter.next();
			if (parameter.getDirection().equals(
					ParameterDirectionKind.RETURN_LITERAL)) {
				if (parameter.getType() != null) {
					returnType = parameter.getType().getName();
				}
				if (parameter.isMultivalued()) {
					returnType = returnType + "[]";
				}
			} else {
				if (params.length() > 1) {
					params += ", ";
				}
				String type = "";
				if (parameter.getType() != null) {
					type = parameter.getType().getName();
				}
				params += parameter.getName();
				if (parameter.isMultivalued()) {
					params += "[]";
				}
				params += ":" + type;
			}
		}
		return operation.getName() + params + ") : " + returnType;
	}

	public static String associationLabel(Association association,
			boolean source, String cardinality) {
		List memberEnds = association.getMemberEnds();
		Iterator i = memberEnds.iterator();
		if (source) {
			i.next();
		}
		Property memberEnd = (Property) i.next();
		StringBuffer text = new StringBuffer(memberEnd.getName());
		text.append("\n");
		text.append(cardinality);
		int length = memberEnd.getName().length();
		while (length-- > 1) {
			text.append(" ");
		}
		return text.toString();

	}

	private static final Map<URL, Image> urlToImage = new HashMap<URL, Image>();

	public static Image getImage(String name) {
		URL url = (URL) UMLEditPlugin.INSTANCE.getImage(name);
		Image image = urlToImage.get(url);
		if (image == null) {
			image = ImageDescriptor.createFromURL(url).createImage();
			urlToImage.put(url, image);
		}
		return image;
	}

	public static void setStringType(Property property) {
		Profile types = property.getModel().getAppliedProfile("types");
		PrimitiveType primitiveType = (PrimitiveType) types
				.getOwnedType("String");
		property.setType(primitiveType);
	}
}
